package cc.aliza.lib.mongo.core.listener;

import cc.aliza.lib.mongo.core.AlizaEntity;
import cc.aliza.lib.mongo.core.AlizaQuery;
import cc.aliza.lib.mongo.core.InternalDao;
import cc.aliza.lib.mongo.core.annotations.Default;
import cc.aliza.lib.mongo.core.annotations.Ref;
import cc.aliza.lib.mongo.core.annotations.RefList;
import cc.aliza.lib.mongo.core.cache.DaoCache;
import cc.aliza.lib.mongo.core.cache.FieldsCache;
import cc.aliza.lib.mongo.core.utils.FieldUtil;
import cc.aliza.lib.mongo.core.utils.Operator;

import java.lang.reflect.*;
import java.util.*;

/**
 * 『给时光以生命·给岁月以文明』
 *
 * @author Zhang Jing (byrdkm17@gmail.com).
 */
@SuppressWarnings("unchecked")
public class CascadeListener implements EntityListener {

    private final List<Field> deleteRefFields = new ArrayList<>();
    private final List<Field> deleteRefListFields = new ArrayList<>();

    private final List<Field> updateRefFields = new ArrayList<>();
    private final List<Field> updateRefListFields = new ArrayList<>();

    private final List<Field> insertRefFields = new ArrayList<>();
    private final List<Field> insertRefListFields = new ArrayList<>();

    public CascadeListener(Class<?> clazz) {
        Field[] fields = FieldsCache.getInstance().get(clazz);
        Arrays.stream(fields).filter(o -> {
            Ref ref = o.getAnnotation(Ref.class);
            return ref != null;
        }).forEach(o -> {
            Ref ref = o.getAnnotation(Ref.class);
            if (ref.cascade().toUpperCase().contains(Default.CASCADE_DELETE)) {
                deleteRefFields.add(o);
            }
            if (ref.cascade().toUpperCase().contains(Default.CASCADE_UPDATE)) {
                updateRefFields.add(o);
            }
            if (ref.cascade().toUpperCase().contains(Default.CASCADE_CREATE)) {
                insertRefFields.add(o);
            }
        });

        Arrays.stream(fields).filter(o -> {
            RefList refList = o.getAnnotation(RefList.class);
            return refList != null;
        }).forEach(o -> {
            RefList refList = o.getAnnotation(RefList.class);
            if (refList.cascade().toUpperCase().contains(Default.CASCADE_DELETE)) {
                deleteRefListFields.add(o);
            }
            if (refList.cascade().toUpperCase().contains(Default.CASCADE_UPDATE)) {
                updateRefListFields.add(o);
            }
            if (refList.cascade().toUpperCase().contains(Default.CASCADE_CREATE)) {
                insertRefListFields.add(o);
            }
        });
    }

    private void processDeleteRef(AlizaEntity entity, Field field) {
        Object value = FieldUtil.get(entity, field);
        if (value != null) {
            InternalDao dao = DaoCache.getInstance().get(field.getType());
            dao.remove(value);
        }
    }

    private void processSaveRef(AlizaEntity entity, Field field) {
        Object value = FieldUtil.get(entity, field);
        if (value != null) {
            InternalDao dao = DaoCache.getInstance().get(field.getType());
            dao.save(value);
        }
    }

    private void processDeleteRefList(AlizaEntity entity, Field field) {
        Object value = FieldUtil.get(entity, field);
        if (value != null) {

            List<String> ids = new ArrayList<>();
            Class<?> clazz = null;
            Class<?> type = field.getType();

            if (type.isArray()) {
                clazz = type.getComponentType();
                ids = getArrayIds(value);
            } else {
                ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
                Type[] types = parameterizedType.getActualTypeArguments();
                int len = types.length;
                if (len == 1) {
                    clazz = (Class) types[0];
                    ids = getCollectionIds(value);
                }
                if (len == 2) {
                    clazz = (Class) types[1];
                    ids = getMapIds(value);
                }
            }

            if (clazz != null) {
                InternalDao dao = DaoCache.getInstance().get(clazz);
                AlizaQuery query = dao.query().in(Operator.ID, ids);
                dao.remove(query);
            }

        }
    }

    private void processSaveRefList(AlizaEntity entity, Field field) {
        Object value = FieldUtil.get(entity, field);
        if (value != null) {

            Class<?> type = field.getType();

            if (type.isArray()) {

                int len = Array.getLength(value);
                for (int i = 0; i < len; i++) {
                    InternalDao dao = DaoCache.getInstance().get(FieldUtil.getRealType(field, type.getComponentType(), Array.get(value, i)));
                    dao.save(Array.get(value, i));
                }
            } else {
                ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
                Type[] types = parameterizedType.getActualTypeArguments();
                int len = types.length;
                if (len == 1) {
                    ((Collection) value).stream().forEach(o -> {
                        Class<?> clazz;
                        if (types[0] instanceof WildcardType) {
                            clazz = (Class) ((WildcardType) types[0]).getUpperBounds()[0];
                        } else {
                            clazz = (Class) types[0];
                        }
                        InternalDao dao = DaoCache.getInstance().get(FieldUtil.getRealType(field, clazz, o));
                        dao.save(o);
                    });
                }
                if (len == 2) {
                    ((Map) value).forEach((k, v) -> {
                        InternalDao dao = DaoCache.getInstance().get(FieldUtil.getRealType(field, (Class) types[1], v));
                        dao.save(v);
                    });
                }
            }
        }
    }

    private List<String> getArrayIds(Object value) {
        int len = Array.getLength(value);
        List<String> ids = new ArrayList<>();
        for (int i = 0; i < len; i++) {
            Object item = Array.get(value, i);
            if (item != null) {
                AlizaEntity ent = (AlizaEntity) item;
                ids.add(ent.getId());
            }
        }
        return ids;
    }

    private List<String> getCollectionIds(Object value) {
        Collection<AlizaEntity> collection = (Collection<AlizaEntity>) value;
        List<String> ids = new ArrayList<>();
        collection.stream().filter(o -> o != null).forEach(o -> ids.add(o.getId()));
        return ids;
    }

    private List<String> getMapIds(Object value) {
        Map<Object, AlizaEntity> map = (Map<Object, AlizaEntity>) value;
        return getCollectionIds(map.values());
    }

    @Override
    public void entityInserted(AlizaEntity entity) {
        insertRefFields.stream().forEach(o -> processSaveRef(entity, o));
        insertRefListFields.stream().forEach(o -> processSaveRefList(entity, o));
    }

    @Override
    public void entityUpdated(AlizaEntity entity) {
        updateRefFields.stream().forEach(o -> processSaveRef(entity, o));
        updateRefListFields.stream().forEach(o -> processSaveRefList(entity, o));
    }

    @Override
    public void entityDeleted(AlizaEntity entity) {
        deleteRefFields.stream().forEach(o -> processDeleteRef(entity, o));
        deleteRefListFields.stream().forEach(o -> processDeleteRefList(entity, o));
    }
}
